/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.file;

import java.io.PrintWriter;

import com.android.dx.dex.code.DalvCode;
import com.android.dx.dex.code.DalvInsnList;
import com.android.dx.dex.code.LocalList;
import com.android.dx.dex.code.PositionList;
import com.android.dx.rop.cst.CstMethodRef;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.ExceptionWithContext;

public class DebugInfoItem extends OffsettedItem {

	/** the required alignment for instances of this class */
	private static final int ALIGNMENT = 1;

	private static final boolean ENABLE_ENCODER_SELF_CHECK = false;

	/** {@code non-null;} the code this item represents */
	private final DalvCode code;

	private byte[] encoded;

	private final boolean isStatic;
	private final CstMethodRef ref;

	public DebugInfoItem(DalvCode code, boolean isStatic, CstMethodRef ref) {
		// We don't know the write size yet.
		super(ALIGNMENT, -1);

		if (code == null) {
			throw new NullPointerException("code == null");
		}

		this.code = code;
		this.isStatic = isStatic;
		this.ref = ref;
	}

	/** {@inheritDoc} */
	@Override
	public ItemType itemType() {
		return ItemType.TYPE_DEBUG_INFO_ITEM;
	}

	/** {@inheritDoc} */
	@Override
	public void addContents(DexFile file) {
		// No contents to add.
	}

	/** {@inheritDoc} */
	@Override
	protected void place0(Section addedTo, int offset) {
		// Encode the data and note the size.

		try {
			encoded = encode(addedTo.getFile(), null, null, null, false);
			setWriteSize(encoded.length);
		} catch (RuntimeException ex) {
			throw ExceptionWithContext.withContext(ex,
					"...while placing debug info for " + ref.toHuman());
		}
	}

	/** {@inheritDoc} */
	@Override
	public String toHuman() {
		throw new RuntimeException("unsupported");
	}

	/**
	 * Writes annotations for the elements of this list, as zero-length. This is
	 * meant to be used for dumping this instance directly after a code dump
	 * (with the real local list actually existing elsewhere in the output).
	 * 
	 * @param file
	 *            {@code non-null;} the file to use for referencing other
	 *            sections
	 * @param out
	 *            {@code non-null;} where to annotate to
	 * @param prefix
	 *            {@code null-ok;} prefix to attach to each line of output
	 */
	public void annotateTo(DexFile file, AnnotatedOutput out, String prefix) {
		encode(file, prefix, null, out, false);
	}

	/**
	 * Does a human-friendly dump of this instance.
	 * 
	 * @param out
	 *            {@code non-null;} where to dump
	 * @param prefix
	 *            {@code non-null;} prefix to attach to each line of output
	 */
	public void debugPrint(PrintWriter out, String prefix) {
		encode(null, prefix, out, null, false);
	}

	/** {@inheritDoc} */
	@Override
	protected void writeTo0(DexFile file, AnnotatedOutput out) {
		if (out.annotates()) {
			/*
			 * Re-run the encoder to generate the annotations, but write the
			 * bits from the original encode
			 */

			out.annotate(offsetString() + " debug info");
			encode(file, null, null, out, true);
		}

		out.write(encoded);
	}

	/**
	 * Performs debug info encoding.
	 * 
	 * @param file
	 *            {@code null-ok;} file to refer to during encoding
	 * @param prefix
	 *            {@code null-ok;} prefix to attach to each line of output
	 * @param debugPrint
	 *            {@code null-ok;} if specified, an alternate output for
	 *            annotations
	 * @param out
	 *            {@code null-ok;} if specified, where annotations should go
	 * @param consume
	 *            whether to claim to have consumed output for {@code out}
	 * @return {@code non-null;} the encoded array
	 */
	private byte[] encode(DexFile file, String prefix, PrintWriter debugPrint,
			AnnotatedOutput out, boolean consume) {
		byte[] result = encode0(file, prefix, debugPrint, out, consume);

		if (ENABLE_ENCODER_SELF_CHECK && (file != null)) {
			try {
				DebugInfoDecoder.validateEncode(result, file, ref, code,
						isStatic);
			} catch (RuntimeException ex) {
				// Reconvert, annotating to System.err.
				encode0(file, "", new PrintWriter(System.err, true), null,
						false);
				throw ex;
			}
		}

		return result;
	}

	/**
	 * Helper for {@link #encode} to do most of the work.
	 * 
	 * @param file
	 *            {@code null-ok;} file to refer to during encoding
	 * @param prefix
	 *            {@code null-ok;} prefix to attach to each line of output
	 * @param debugPrint
	 *            {@code null-ok;} if specified, an alternate output for
	 *            annotations
	 * @param out
	 *            {@code null-ok;} if specified, where annotations should go
	 * @param consume
	 *            whether to claim to have consumed output for {@code out}
	 * @return {@code non-null;} the encoded array
	 */
	private byte[] encode0(DexFile file, String prefix, PrintWriter debugPrint,
			AnnotatedOutput out, boolean consume) {
		PositionList positions = code.getPositions();
		LocalList locals = code.getLocals();
		DalvInsnList insns = code.getInsns();
		int codeSize = insns.codeSize();
		int regSize = insns.getRegistersSize();

		DebugInfoEncoder encoder = new DebugInfoEncoder(positions, locals,
				file, codeSize, regSize, isStatic, ref);

		byte[] result;

		if ((debugPrint == null) && (out == null)) {
			result = encoder.convert();
		} else {
			result = encoder.convertAndAnnotate(prefix, debugPrint, out,
					consume);
		}

		return result;
	}
}
